﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.AddIn.Hosting;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using BlueCurve.AddinHostView;
using BlueCurve.AddInsLoad;
using BlueCurve.Common;
using BlueCurve.DownloadAttributes;
using BlueCurve.EnumAction;
using BlueCurve.WebCrawlerRequest;
using System.Collections;
using WebCrawler.Events;
using BlueCurve.Common.Threads;
using System.Configuration;

namespace WebCrawler
{
    /// <summary>
    /// Class de chargement des Addins et de telechargement des documents
    /// </summary>
    public class Downloader
    {
        #region 'Configuration'

        /// <summary>
        /// Permet d'arreter le thread
        /// </summary>
        private bool m_IsRun = true;
        /// <summary>
        /// Nombre de threads tournant
        /// </summary>
        private long m_AliveThreads = 1;
        /// <summary>
        /// Instance de la bibliothèque de chargement
        /// des AddIns
        /// </summary>
        private PluginsLoader<AddinProtocolViewHostSide> m_Pluginsloader = new PluginsLoader<AddinProtocolViewHostSide>(@"AddIns");
        /// <summary>
        /// Dictionnaire des données à télécharger
        /// </summary>
        private Queue m_DataQueue = new Queue();
        /// <summary>
        /// Instance de la class
        /// </summary>
        private ResponseTreat m_ResponseTreat { get; set; }
        /// <summary>
        /// Pool de threads
        /// </summary>
        private SimpleLockThreadPool m_ThreadPool = new SimpleLockThreadPool(Environment.ProcessorCount * Convert.ToInt32(ConfigurationManager.AppSettings["ThreadsPerProcessor"]));
        /// <summary>
        /// Délégue de l'evenement
        /// </summary>
        /// <param name="sender">objet de l'evenement</param>
        /// <param name="e">Données de l'evenement</param>
        public delegate void DeleteUriFromDatabaseEventHandler(object sender, DeleteFromCrawlerCache e);
        /// <summary>
        /// Evenement suppression de la base de données
        /// </summary>
        public event DeleteUriFromDatabaseEventHandler DeleteUriFromDatabaseEvent;


        /// <summary>
        /// Constructeur
        /// </summary>
        public Downloader(ResponseTreat _treat)
        {
            this.m_ResponseTreat = _treat;
            this.m_Pluginsloader.UpdateAddins();
        }

        /// <summary>
        /// Permet de stoper le thread
        /// </summary>
        public bool IsStop
        {
            set { this.m_IsRun = value; }
        }

        /// <summary>
        /// Retourne le nombre de thread en cours
        /// </summary>
        public long AliveThreads
        {
            get { return Interlocked.Read(ref this.m_AliveThreads); }
        }

        #endregion




        #region 'Functions'

        /// <summary>
        /// Fonction principale de la class
        /// </summary>
        public void RunDownloader()
        {
            // Rechargement de la pile
            this.Reload();

            while (this.m_IsRun)
            {
                // Nombre de documents en file d'attente
                if (this.m_DataQueue.Count == 0)
                {
                    // Nombre de documents à téléchargers
                    Nullable<int> countQueue = null;
                    // Données de la pile
                    List<_Tempdata> tempQueue = null;
                    using (WebCrawlerdbDataContext db = new WebCrawlerdbDataContext())
                    {
                        tempQueue = (from v in db.Cache
                                    select new _Tempdata
                                    {
                                        ID = v.ID,
                                        LASTMODIFIED = v.LASTMODIFIED,
                                        LASTUPDATE = v.LASTUPDATE,
                                        OPTIONS = v.OPTIONS,
                                        REINDEXTIME = v.REINDEXTIME,
                                        STATE = v.STATE,
                                        URI = v.URI,
                                        X509 = v.X509,
                                        X509V2 = v.X509V2
                                    }).ToList();
                                        
                                        // db.SelectAllDataInCrawlerCache();
                        countQueue = db.CountItemsInCrawlerCache();
                    }
                   
                    // Chargement de la pile si il y a des données
                    if (countQueue.HasValue && tempQueue != null)
                    {
                        this.m_DataQueue = new Queue(tempQueue);
                        while (this.m_DataQueue.Count > 0)
                        {
                            this.m_ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), this.m_DataQueue.Dequeue());
                        }
                    }
                }
              
                //Thread.Sleep(6 * 60 * 1000); // six minutes
                while (this.m_AliveThreads > 1) { Thread.Sleep(150); } // Wait while threadpool is not empty
            }

            while (this.AliveThreads > 1) { Thread.Sleep(100); }
            if (this.m_DataQueue.Count > 0)
                this.Save();

            Interlocked.Decrement(ref this.m_AliveThreads);
        }


        /// <summary>
        /// Fonction attaché au pool de threads
        /// </summary>
        /// <param name="state">State</param>
        private void DoWork(object state)
        {
            Interlocked.Increment(ref this.m_AliveThreads);
            _Tempdata datas = (_Tempdata)state;
            try
            {
                CommonFunctions common = new CommonFunctions();
                // Récupération du type de requete (http, ftp etc...)
                string scheme = common.GetScheme(datas.URI);
                // Token de l'AddIn
                AddInToken addinToken = this.m_Pluginsloader.GetAddInToken(scheme);

                if (addinToken != null)
                {
                    // Activation du plugin CurrentAppDomain 
                    AddinProtocolViewHostSide addinInstance = addinToken.Activate<AddinProtocolViewHostSide>(AppDomain.CurrentDomain);

                    DateTime _lastmodified;
                    DateTime _lastupdate;

                    if (!datas.LASTMODIFIED.HasValue)
                        _lastmodified = DateTime.MinValue;
                    else
                        _lastmodified = datas.LASTMODIFIED.Value;

                    if (!datas.LASTUPDATE.HasValue)
                        _lastupdate = DateTime.MinValue;
                    else
                        _lastupdate = datas.LASTUPDATE.Value;

                    RequestState rstate = addinInstance.Download(new Attributes()
                    {
                        LastModified = _lastmodified,
                        LastUpdate = _lastupdate,
                        ReindexTime = datas.REINDEXTIME,
                        Url = datas.URI,
                        X509 = datas.X509,
                        X509V2 = datas.X509V2,
                        Options = datas.OPTIONS,
                        Action = (IndexAction)Enum.Parse(typeof(IndexAction), Enum.GetName(typeof(IndexAction), datas.STATE), true)
                    }, MakeCertificates(datas.X509, true), MakeCertificates(datas.X509V2, false));

                    //Fermeture du canal avec le plugin
                    AddInController.GetAddInController(addinInstance).Shutdown();

                    if (rstate != null)
                        Console.WriteLine("Pompé avec succes");
                    else
                        Console.WriteLine("Probleme lors du téléchargement");

                    // Ajout des données dans la pile de traitement
                    this.m_ResponseTreat.Add(rstate);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                // Pauvre sql server
                // Suppression du lien de la base de données
                if (DeleteUriFromDatabaseEvent != null)
                    DeleteUriFromDatabaseEvent("", new DeleteFromCrawlerCache() { Uri = datas.URI });

                Interlocked.Decrement(ref this.m_AliveThreads);
            }
        }





        /// <summary>
        /// Créer un tableau de certificats X509 a utiliser avec la connection
        /// </summary>
        /// <param name="cert">Chemin vers le fichier X509 ou le dossier les(s) contenant(s)</param>
        /// <param name="e">Permets de savoir de quel type de certificat il s'agit</param>
        /// <returns>X509Certificate[]</returns>
        private X509Certificate[] MakeCertificates(string cert, bool e)
        {
            try
            {
                if (!File.Exists(cert) || !Directory.Exists(cert))
                    return null;

                if (File.GetAttributes(cert) == FileAttributes.Directory)
                {
                    string[] files = Directory.GetFiles(cert);
                    if (files.Length == 0)
                        return null;

                    List<X509Certificate> certs = new List<X509Certificate>();
                    for (int i = 0; i < files.Length; i++)
                    {
                        if (e)
                            certs.Add(X509Certificate.CreateFromCertFile(files[i]));
                        else
                            certs.Add(X509Certificate2.CreateFromCertFile(files[i]));
                    }
                    return certs.ToArray();
                }
                if (e)
                    return new X509Certificate[] { X509Certificate.CreateFromCertFile(cert) };
                else
                    return new X509Certificate[] { X509Certificate2.CreateFromCertFile(cert) };
            }
            catch
            {
                return null;
            }
        }





        /// <summary>
        /// Sauvegarde de la pile d'indexation
        /// </summary>
        private void Save()
        {
            using (Stream stream = File.Open("DownloaderQueue.bin", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this.m_DataQueue);
                stream.Close();
            }
        }





        /// <summary>
        /// Restauration de la pile d'indexation
        /// </summary>
        private void Reload()
        {
            if (File.Exists("DownloaderQueue.bin"))
            {
                Queue _queue = null;
                using (Stream stream = File.Open("DownloaderQueue.bin", FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    _queue = (Queue)formatter.Deserialize(stream);
                    stream.Close();
                }

                if (_queue.Count > 0)
                    this.m_DataQueue = new Queue(_queue);

                File.Delete("TreatMentQueue.bin");
            }
        }

        /// <summary>
        /// Données temporaire
        /// </summary>
        [Serializable]
        internal class _Tempdata
        {
            internal int ID { get; set; }
            internal string URI { get; set; }
            internal int STATE { get; set; }
            internal Nullable<DateTime> LASTUPDATE { get; set; }
            internal Nullable<DateTime> LASTMODIFIED { get; set; }
            internal int REINDEXTIME { get; set; }
            internal string X509 { get; set; }
            internal string X509V2 { get; set; }
            internal string OPTIONS { get; set; }

            internal _Tempdata()
            {
            }
        }

        #endregion
    }
}
